package com.example.gfile.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.gfile.constant.DateFormat;
import com.example.gfile.entity.FileDetail;
import com.example.gfile.entity.FileName;
import com.example.gfile.entity.FileTemplate;
import com.example.gfile.entity.FileVo;
import com.example.gfile.entity.Result;
import com.example.gfile.entity.Template;
import com.example.gfile.exception.GFileException;
import com.example.gfile.mapper.TemplateMapper;
import com.example.gfile.pool.BatchCreateFileThread;
import com.example.gfile.random.DatabaseColumn;
import com.example.gfile.util.JsonUtil;
import com.example.gfile.util.SpringContextUtil;
import com.example.gfile.util.ZipUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Service
@Slf4j
public class FileService {

    private static final String BATCH_NO = "${batchNo}";
    @Value("${root-directory}")
    private String rootDirectory;
    @Resource
    private Executor threadPoolTaskExecutor;
    @Resource
    private SqlMapperService sqlMapperService;
    @Resource
    private TemplateMapper fileTemplateMapper;

    public void process(FileTemplate fileTemplate) {
        //名字日期替换
        Consumer<FileTemplate> templateConsumer = fileNameDateReplace()
                //加载数据库字段配置
                .andThen(loadDataBaseColumn())
                //批次号组装
                .andThen(fileBatchBuild())
                //替换批次号
                .andThen(fileNameBatchReplace())
                //批量生成文件
                .andThen(batchGenerateFile())
                //批量生成前置文件
                .andThen(batchGeneratePreFile())
                //文件压缩
                .andThen(FIleCompress());
        templateConsumer.accept(fileTemplate);
    }

    private Consumer<FileTemplate> batchGeneratePreFile() {
        return fileTemplate -> {
            FileName fileName = fileTemplate.getFileName();
            boolean preFileSwitch = fileName.isPreFileSwitch();
            if (!preFileSwitch) {
                return;
            }
            String preFileNameSuffix = fileName.getPreFileNameSuffix();
            if (StringUtils.isEmpty(preFileNameSuffix)) {
                return;
            }
            FileName templateFileName = fileTemplate.getFileName();
            List<String> listFileName = templateFileName.getListFileName();
            try {
                for (String file : listFileName) {
                    String filePath = String.join("", rootDirectory, file, preFileNameSuffix);
                    FileUtils.touch(new File(filePath));
                }
            } catch (IOException e) {
                throw new GFileException(e.getMessage(), e);
            }
        };
    }

    private Consumer<FileTemplate> FIleCompress() {
        return fileTemplate -> {
            FileName fileName = fileTemplate.getFileName();
            boolean fileCompressSwitch = fileName.isFileCompressSwitch();
            if (!fileCompressSwitch) {
                return;
            }
            String fileCompressName = fileName.getFileCompressName();
            if (StringUtils.isEmpty(fileCompressName)) {
                return;
            }
            File directory = new File(rootDirectory);
            File[] listFiles = directory.listFiles();
            Stream<File> files = Stream.of(listFiles);
            List<String> listFilesPath = files.map(file -> file.getAbsolutePath()).collect(Collectors.toList());
            ZipUtil.doZip(listFilesPath, fileCompressName);
        };
    }

    private Consumer<FileTemplate> batchGenerateFile() {
        return fileTemplate -> {
            FileName templateFileName = fileTemplate.getFileName();
            List<String> listFileName = templateFileName.getListFileName();
            CountDownLatch countDownLatch = new CountDownLatch(listFileName.size());
            for (String fileName : listFileName) {
                File file = new File(rootDirectory + fileName);
                threadPoolTaskExecutor.execute(new BatchCreateFileThread(file, fileTemplate, countDownLatch));
            }
            try {
                countDownLatch.await();
            } catch (InterruptedException e) {
                throw new GFileException(e.getMessage(), e);
            }
        };
    }

    private Consumer<FileTemplate> fileNameBatchReplace() {
        return fileTemplate -> {
            FileName templateFileName = fileTemplate.getFileName();
            List<String> listBatchNo = templateFileName.getListBatchNo();
            String fileName = templateFileName.getFileName();
            List<String> listFileName = new ArrayList<>(listBatchNo.size());

            if (!fileName.contains(BATCH_NO)) {
                listFileName.add(fileName);
            } else {
                for (String batchNo : listBatchNo) {
                    String fullFileName = fileName.replace(BATCH_NO, batchNo);
                    listFileName.add(fullFileName);
                }
            }
            templateFileName.setListFileName(listFileName);
        };
    }

    private Consumer<FileTemplate> fileBatchBuild() {
        return fileTemplate -> {
            FileName templateFileName = fileTemplate.getFileName();
            String batchNo = templateFileName.getBatchNo();
            int fileCount = templateFileName.getFileCount();
            List batchNoList = new ArrayList(fileCount);

            if (StringUtils.isNotEmpty(batchNo)) {
                //批次号从后面追加
                String prefixStr = batchNo.substring(0, batchNo.length() - 1);
                for (int i = 1; i <= fileCount; i++) {
                    String appendStr = prefixStr + i;
                    //判断追加后的字符串长度是否大于原字符串长度
                    if (appendStr.length() > batchNo.length()) {
                        //大于原字符串从后向前截取
                        prefixStr = prefixStr.substring(0, prefixStr.length() - 1);
                    }
                    //从后往前追加
                    String pad = StringUtils.rightPad(prefixStr, batchNo.length(), String.valueOf(i));
                    batchNoList.add(pad);
                }
            }
            templateFileName.setListBatchNo(batchNoList);
        };
    }


    public Consumer<FileTemplate> fileNameDateReplace() {
        return fileTemplate -> {
            FileName templateFileName = fileTemplate.getFileName();
            String fileNameDate = templateFileName.getFileNameDate();
            String fileName = templateFileName.getFileName();
            if (StringUtils.isEmpty(fileNameDate)) {
                return;
            }
            Calendar calendar = getCalendar(fileNameDate);
            String year = String.valueOf(calendar.get(Calendar.YEAR));
            String month = String.valueOf(calendar.get(Calendar.MONTH) + 1);
            String date = String.valueOf(calendar.get(Calendar.DATE));
            String hour = StringUtils.leftPad(String.valueOf(calendar.get(Calendar.HOUR_OF_DAY)), 2, "0");
            String minute = StringUtils.leftPad(String.valueOf(calendar.get(Calendar.MINUTE)), 2, "0");
            String second = StringUtils.leftPad(String.valueOf(calendar.get(Calendar.SECOND)), 2, "0");

            String replaceFileName = fileName.replace(DateFormat.YYYY, year)
                    .replace(DateFormat.MM, month)
                    .replace(DateFormat.DD, date)
                    .replace(DateFormat.HH, hour)
                    .replace(DateFormat.MINUTE, minute)
                    .replace(DateFormat.SS, second);
            templateFileName.setFileName(replaceFileName);
        };
    }


    private Calendar getCalendar(String fileNameDate) {
        Calendar calendar = Calendar.getInstance();
        try {
            SimpleDateFormat format = new SimpleDateFormat(DateFormat.FILE_NAME_DATE);
            Date parse = format.parse(fileNameDate);
            calendar.setTime(parse);
        } catch (ParseException e) {
            log.error("文件日期格式化异常：{}", e.getMessage());
            throw new GFileException(e.getMessage(), e);
        }
        return calendar;
    }

    private Consumer<FileTemplate> loadDataBaseColumn() {
        return fileTemplate -> {
            List<FileDetail> detailList = fileTemplate.getFileDetailList();
            Map<String, List<List<Map<String, Object>>>> dbColumnList = new HashMap<>();
            for (int i = 0; i < detailList.size(); i++) {
                FileDetail fileDetail = detailList.get(i);
                String sql = fileDetail.getSql();
                if (StringUtils.isEmpty(sql)) {
                    return;
                }
                String dbId = fileDetail.getDbId();
                List<List<Map<String, Object>>> lists = sqlMapperService.executeQuerySql(sql, dbId);
                dbColumnList.put(String.valueOf(i), lists);
            }
            DatabaseColumn bean = SpringContextUtil.getBean(DatabaseColumn.class);
            bean.setDbColumnList(dbColumnList);
        };
    }

    public Result getAllFile() {
        List<FileVo> fIleVoList = new ArrayList<>();
        try {
            File directory = new File(rootDirectory);
            File[] files = directory.listFiles();
            if (Objects.nonNull(files)) {
                for (File file : files) {
                    FileVo vo = new FileVo();
                    BasicFileAttributes basicFileAttributes = Files.readAttributes(Paths.get(file.getAbsolutePath()), BasicFileAttributes.class);
                    FileTime creationTime = basicFileAttributes.creationTime();
                    long size = basicFileAttributes.size() / 1024;
                    vo.setFileName(file.getName());
                    vo.setPath(rootDirectory);
                    vo.setSize(size + "Kb");
                    vo.setCreateTime(DateFormat.formatStr(creationTime.toMillis(), DateFormat.FILE_NAME_DATE));
                    vo.setLastUpdateTime(DateFormat.formatStr(file.lastModified(), DateFormat.FILE_NAME_DATE));
                    fIleVoList.add(vo);
                }
            }
        } catch (IOException e) {
            throw new GFileException(e.getMessage(), e);
        }
        return Result.success(fIleVoList);
    }

    public Result deleteFile(String fileName, String path) {
        try {
            File file = new File(path + fileName);
            if (file.exists()) {
                file.delete();
            } else {
                return Result.fail("文件不存在");
            }
        } catch (Exception e) {
            throw new GFileException(e.getMessage(), e);
        }
        return Result.success("ok");
    }

    public void save(String fileTemplateName, FileTemplate fileTemplate) {
        Template template = new Template();
        template.setInstId(fileTemplate.getInstId());
        template.setTemplateName(fileTemplateName);
        template.setCreateTime(DateFormat.formatDate(new Date(), DateFormat.FILE_NAME_DATE));
        template.setUpdateTime(DateFormat.formatDate(new Date(), DateFormat.FILE_NAME_DATE));
        template.setTemplate(JsonUtil.toJson(fileTemplate));
        fileTemplateMapper.insert(template);
    }


    public List<Template> getAll(String instId) {
        LambdaQueryWrapper<Template> queryWrapper = new LambdaQueryWrapper();
        if (StringUtils.isNotEmpty(instId)) {
            queryWrapper.eq(Template::getInstId, instId);
        }
        return fileTemplateMapper.selectList(queryWrapper);
    }

    public List<Template> getPage(String instId, int page, int limit) {
        Page<Template> DbPage = new Page(page, limit);
        LambdaQueryWrapper<Template> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotEmpty(instId)) {
            queryWrapper.eq(Template::getInstId, instId);
        }
        Page<Template> selectPage = fileTemplateMapper.selectPage(DbPage, queryWrapper);
        return selectPage.getRecords();
    }

    public int update(String data) {
        Template template = JsonUtil.jsonToObject(data, Template.class);
        template.setUpdateTime(DateFormat.formatDate(new Date(), DateFormat.FILE_NAME_DATE));
        return fileTemplateMapper.updateById(template);
    }

    public Template selectById(String id) {
        return fileTemplateMapper.selectById(id);
    }

    public int delete(String id) {
        return fileTemplateMapper.deleteById(id);
    }

}
